 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * Matthew Conway - Bug 175186
  *******************************************************************************/
 package org.eclipse.ui.externaltools.internal.model;


 import java.util.Map ;

 import org.eclipse.core.resources.ICommand;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IProjectDescription;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceDelta;
 import org.eclipse.core.resources.IResourceDeltaVisitor;
 import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
 import org.eclipse.debug.core.ILaunchManager;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.ui.externaltools.internal.launchConfigurations.ExternalToolsUtil;
 import org.eclipse.ui.externaltools.internal.registry.ExternalToolMigration;
 import org.osgi.framework.Bundle;

 /**
  * This project builder implementation will run an external tool during the
  * build process.
  */
 public final class ExternalToolBuilder extends IncrementalProjectBuilder {
     private final class IgnoreTeamPrivateChanges implements IResourceDeltaVisitor {
         private boolean[] fTrueChange;
         private IgnoreTeamPrivateChanges(boolean[] trueChange) {
             super();
             fTrueChange= trueChange;
         }
         public boolean visit(IResourceDelta visitDelta) throws CoreException {
             IResource resource= visitDelta.getResource();
             if (resource instanceof IFile) {
                 fTrueChange[0]= true;
                 return false;
             }
             return true;
         }
     }

     public static final String ID = "org.eclipse.ui.externaltools.ExternalToolBuilder"; //$NON-NLS-1$;

     private static String buildType = IExternalToolConstants.BUILD_TYPE_NONE;
     
     private static IProject buildProject= null;
     private static IResourceDelta buildDelta= null;
     
     /* (non-Javadoc)
      * @see org.eclipse.core.internal.events.InternalBuilder#build(int, java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
      */
     protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
         if (ExternalToolsPlugin.getDefault().getBundle().getState() != Bundle.ACTIVE) {
             return null;
         }
         
         ILaunchConfiguration config= BuilderUtils.configFromBuildCommandArgs(getProject(), args, new String [1]);
         if (config == null) {
             throw ExternalToolsPlugin.newError(ExternalToolsModelMessages.ExternalToolBuilder_0, null);
         }
         IProject[] projectsWithinScope= null;
         IResource[] resources = ExternalToolsUtil.getResourcesForBuildScope(config);
         if (resources != null) {
             projectsWithinScope= new IProject[resources.length];
             for (int i = 0; i < resources.length; i++) {
                 projectsWithinScope[i]= resources[i].getProject();
             }
         }
         boolean kindCompatible= commandConfiguredForKind(config, kind);
         if (kindCompatible && configEnabled(config)) {
             doBuildBasedOnScope(resources, kind, config, monitor);
         }
         
         return projectsWithinScope;
     }

     private boolean commandConfiguredForKind(ILaunchConfiguration config, int kind) {
         try {
             if (!(config.getAttribute(IExternalToolConstants.ATTR_TRIGGERS_CONFIGURED, false))) {
                 ICommand command= getCommand();
                 //adapt the builder command to make use of the 3.1 support for setting command build kinds
 //this will only happen once for builder/command defined before the support existed
 BuilderUtils.configureTriggers(config, command);
                 IProjectDescription desc= getProject().getDescription();
                 ICommand[] commands= desc.getBuildSpec();
                 int index= getBuilderCommandIndex(commands, command);
                 if (index != -1) {
                     commands[index]= command;
                     desc.setBuildSpec(commands);
                     getProject().setDescription(desc, null);
                     ILaunchConfigurationWorkingCopy copy= config.getWorkingCopy();
                     copy.setAttribute(IExternalToolConstants.ATTR_TRIGGERS_CONFIGURED, true);
                     copy.doSave();
                 }
                 return command.isBuilding(kind);
             }
         } catch (CoreException e) {
            ExternalToolsPlugin.getDefault().log(e);
            return true;
         }
         return true;
     }
     
     private int getBuilderCommandIndex(ICommand[] buildSpec, ICommand command) {
         Map commandArgs= command.getArguments();
         if (commandArgs == null) {
             return -1;
         }
         String handle= (String ) commandArgs.get(BuilderUtils.LAUNCH_CONFIG_HANDLE);
         if (handle == null) {
             return -1;
         }
         for (int i = 0; i < buildSpec.length; ++i) {
             ICommand buildSpecCommand= buildSpec[i];
             if (ID.equals(buildSpecCommand.getBuilderName())) {
                 Map buildSpecArgs= buildSpecCommand.getArguments();
                 if (buildSpecArgs != null) {
                     String buildSpecHandle= (String ) buildSpecArgs.get(BuilderUtils.LAUNCH_CONFIG_HANDLE);
                     if (handle.equals(buildSpecHandle)) {
                         return i;
                     }
                 }
             }
         }
         return -1;
     }

     /**
      * Returns whether the given builder config is enabled or not.
      *
      * @param config the config to examine
      * @return whether the config is enabled
      */
     private boolean configEnabled(ILaunchConfiguration config) {
         try {
             return ExternalToolsUtil.isBuilderEnabled(config);
         } catch (CoreException e) {
             ExternalToolsPlugin.getDefault().log(e);
         }
         return true;
     }

     private void doBuildBasedOnScope(IResource[] resources, int kind, ILaunchConfiguration config, IProgressMonitor monitor) throws CoreException {
         boolean buildForChange = true;
         if (kind != FULL_BUILD) { //scope not applied for full builds
 if (resources != null && resources.length > 0) {
                 buildForChange = buildScopeIndicatesBuild(resources);
             }
         }

         if (buildForChange) {
             launchBuild(kind, config, monitor);
         }
     }
     
     private void launchBuild(int kind, ILaunchConfiguration config, IProgressMonitor monitor) throws CoreException {
         monitor.subTask(NLS.bind(ExternalToolsModelMessages.ExternalToolBuilder_Running__0_____1, new String [] { config.getName()}));
         buildStarted(kind);
         // The default value for "launch in background" is true in debug core. If
 // the user doesn't go through the UI, the new attribute won't be set. This means
 // that existing Ant builders will try to run in the background (and likely conflict with
 // each other) without migration.
 config= ExternalToolMigration.migrateRunInBackground(config);
         config.launch(ILaunchManager.RUN_MODE, monitor);
         buildEnded();
     }

     /**
      * Returns the build type being performed if the
      * external tool is being run as a project builder.
      *
      * @return one of the <code>IExternalToolConstants.BUILD_TYPE_*</code> constants.
      */
     public static String getBuildType() {
         return buildType;
     }
     
     /**
      * Returns the project that is being built and has triggered the current external
      * tool builder. <code>null</code> is returned if no build is currently occurring.
      *
      * @return project being built or <code>null</code>.
      */
     public static IProject getBuildProject() {
         return buildProject;
     }

     /**
      * Returns the <code>IResourceDelta</code> that is being built and has triggered the current external
      * tool builder. <code>null</code> is returned if no build is currently occurring.
      *
      * @return resource delta for the build or <code>null</code>
      */
     public static IResourceDelta getBuildDelta() {
         return buildDelta;
     }
     
     /**
      * Stores the currently active build kind and build project when a build begins
      * @param buildKind
      */
     private void buildStarted(int buildKind) {
         switch (buildKind) {
             case IncrementalProjectBuilder.INCREMENTAL_BUILD :
                 buildType = IExternalToolConstants.BUILD_TYPE_INCREMENTAL;
                 buildDelta = getDelta(getProject());
                 break;
             case IncrementalProjectBuilder.FULL_BUILD :
                 buildType = IExternalToolConstants.BUILD_TYPE_FULL;
                 break;
             case IncrementalProjectBuilder.AUTO_BUILD :
                 buildType = IExternalToolConstants.BUILD_TYPE_AUTO;
                 buildDelta = getDelta(getProject());
                 break;
             case IncrementalProjectBuilder.CLEAN_BUILD :
                 buildType = IExternalToolConstants.BUILD_TYPE_CLEAN;
                 break;
             default :
                 buildType = IExternalToolConstants.BUILD_TYPE_NONE;
                 break;
         }
         buildProject= getProject();
     }
     
     /**
      * Clears the current build kind, build project and build delta when a build finishes.
      */
     private void buildEnded() {
         buildType= IExternalToolConstants.BUILD_TYPE_NONE;
         buildProject= null;
         buildDelta= null;
     }
     
     private boolean buildScopeIndicatesBuild(IResource[] resources) {
         for (int i = 0; i < resources.length; i++) {
             IResourceDelta delta = getDelta(resources[i].getProject());
             if (delta == null) {
                 //project just added to the workspace..no previous build tree
 return true;
             }
             IPath path= resources[i].getProjectRelativePath();
             IResourceDelta change= delta.findMember(path);
             if (change != null) {
                 final boolean[] trueChange= new boolean[1];
                 trueChange[0]= false;
                 try {
                     change.accept(new IgnoreTeamPrivateChanges(trueChange));
                 } catch (CoreException e) {
                     ExternalToolsPlugin.getDefault().log("Internal error resolving changed resources during build", e); //$NON-NLS-1$
 }
                 
                 return trueChange[0]; //filtered out team private changes
 }
         }
         return false;
     }
     
     protected void clean(IProgressMonitor monitor) throws CoreException {
         ICommand command= getCommand();
         ILaunchConfiguration config= BuilderUtils.configFromBuildCommandArgs(getProject(), command.getArguments(), new String [1]);
         if (!configEnabled(config)) {
             return;
         }
         
         if ((!config.getAttribute(IExternalToolConstants.ATTR_TRIGGERS_CONFIGURED, false))) {
             //old behavior
 super.clean(monitor);
             return;
         }
     
         launchBuild(IncrementalProjectBuilder.CLEAN_BUILD, config, monitor);
     }
 }
